#include "platform.h"
#include "compress.h" // zlib_version()
#include "grfnode.h"
#include "grffile.h"
#include "grftreenode.h"
#include "libgrf.h"
#include <string> // std::string
#include <memory> // std::auto_ptr<>


GRFEXPORT uint32_t grf_version(void)
{
	return VERSION_MAJOR << 16 | VERSION_MINOR << 8 | VERSION_REVISION << 0;
}


GRFEXPORT char* grf_versionstring(void)
{
	static char vstr[128];
	return grf_versionstring_r(vstr, sizeof(vstr));
}


GRFEXPORT char* grf_versionstring_r(char* vstr, size_t buflen)
{
	snprintf(vstr, buflen, "Gravity Ragnarok Files lib v%d.%d.%d by %s\nBundled zlib version %s", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_AUTHOR, zlib_version_string());
	return vstr;
}


GRFEXPORT GrfFile* grf_new(const char* filename, bool writemode)
{
	return GrfFile::grf_new(filename, writemode);
}


GRFEXPORT GrfFile* grf_new_by_fd(int fd, bool writemode)
{
	return GrfFile::grf_new_by_fd(fd, writemode);
}


GRFEXPORT GrfFile* grf_load(const char* filename, bool writemode)
{
	GrfFile* handler = GrfFile::grf_new(filename, writemode);
	if (handler == NULL)
		return NULL;

	if (handler->GrfFile::grf_load_from_new() == NULL) {
		delete handler;
		return NULL;
	}

	return handler;
}


GRFEXPORT GrfFile* grf_load_from_new(GrfFile* handler)
{
	return handler->GrfFile::grf_load_from_new();
}


GRFEXPORT bool grf_save(GrfFile* handler)
{
	return handler->GrfFile::grf_save();
}


GRFEXPORT void grf_free(GrfFile* handler)
{
	delete handler;
}


GRFEXPORT void grf_set_callback(GrfFile* handler, grf_callback* callback, void* etc)
{
	return handler->GrfFile::grf_set_callback(callback, etc);
}


GRFEXPORT void grf_set_compression_level(GrfFile* handler, int level)
{
	return handler->GrfFile::grf_set_compression_level(level);
}


GRFEXPORT uint32_t grf_filecount(GrfFile* handler)
{
	return handler->GrfFile::filecount();
}


GRFEXPORT uint32_t grf_wasted_space(GrfFile* handler)
{
	return handler->GrfFile::wasted_space();
}


GRFEXPORT GrfNode* grf_file_add(GrfFile* handler, const char* filename, void* ptr, size_t size)
{
	return handler->GrfFile::grf_file_add(filename, ptr, size);
}


/// Convenience function.
GRFEXPORT GrfNode* grf_file_add_fd(GrfFile* handler, const char* filename, int fd)
{
	struct stat s;
	if( fstat(fd, &s) != 0 )
		return NULL;

	uint8_t* ptr = new uint8_t[s.st_size];
	if( ptr == NULL )
		return NULL;

	if( read(fd, ptr, s.st_size) != s.st_size )
	{
		delete[] ptr;
		return NULL;
	}

	GrfNode* res = grf_file_add(handler, filename, ptr, s.st_size);
	delete[] ptr;
	return res;
}


/// Convenience function.
GRFEXPORT GrfNode* grf_file_add_path(GrfFile* handler, const char* filename, const char* real_filename)
{
	int fd = open(real_filename, O_RDONLY | O_BINARY);
	if( fd == -1 )
		return NULL;

	GrfNode* res = grf_file_add_fd(handler, filename, fd);
	close(fd);
	return res;
}


GRFEXPORT GrfNode* grf_get_file(GrfFile* handler, const char* filename)
{
	return handler->GrfFile::grf_get_file(filename);
}


GRFEXPORT const char* grf_file_get_filename(GrfNode* handler)
{
	return handler->GrfNode::filename();
}


/// Convenience function.
GRFEXPORT const char* grf_file_get_basename(GrfNode* handler)
{
	const char* fname = grf_file_get_filename(handler);
	const char* p = NULL;
	if( p = NULL )
		p = strrchr(fname, '/');
	if( p == NULL )
		p = strrchr(fname, '\\');
	if( p == NULL )
		p = fname;

	return p;
}


GRFEXPORT uint32_t grf_file_get_size(GrfNode* handler)
{
	return handler->GrfNode::get_size();
}


GRFEXPORT uint32_t grf_file_get_storage_pos(GrfNode* handler)
{
	return handler->GrfNode::storage_pos();
}


GRFEXPORT uint32_t grf_file_get_storage_size(GrfNode* handler)
{
	return handler->GrfNode::storage_size();
}


GRFEXPORT uint32_t grf_file_get_storage_flags(GrfNode* handler)
{
	return handler->GrfNode::storage_flags();
}


GRFEXPORT uint32_t grf_file_get_contents(GrfNode* fhandler, void* target)
{
	return fhandler->parent->grf_file_get_contents(fhandler, target);
}


/// Convenience function.
GRFEXPORT uint32_t grf_file_put_contents_to_fd(GrfNode* file, int fd)
{
	uint32_t size = grf_file_get_size(file);
	if( size == 0 )
		return 0;

	uint8_t* ptr = new uint8_t[size];
	if( ptr == NULL )
		return 0;

	if( grf_file_get_contents(file, ptr) != size )
	{
		delete[] ptr;
		return 0;
	}

	uint32_t p = 0;
	while( p < size )
	{
		size_t i = write(fd, ptr+p, size-p);
		if( i <= 0 )
		{
			delete[] ptr;
			return 0;
		}
		p += i;
	}

	delete[] ptr;
	return size;
}


/// Create all directories leading up to the given path (mkdir -p).
static bool mkdirpath(const char* path)
{
	std::string n = path;
	int pos = n.rfind('/');
	if( pos == n.npos )
		pos = n.rfind('\\');
	if( pos != n.npos )
		n.resize(pos);
	else
		n.assign(".");
	const char* b = n.c_str();

	struct stat s;
	memset(&s, 0, sizeof(s));
	stat(b, &s);

	if( (s.st_mode & S_IFDIR) == S_IFDIR )
		return true; // already good

	if( mkdir(b) == 0 ) 
		return true; // success

	// try to recursively construct base path first
	if( !mkdirpath(b) )
		return false; // something broke

	// now try again
	if( mkdir(b) != 0 )
		return false; // something broke

	return true;
}


/// Convenience function.
GRFEXPORT bool grf_put_contents_to_file(GrfNode* file, const char* fn)
{
	mkdirpath(fn);

	int fd = open(fn, O_BINARY|O_WRONLY|O_CREAT);
	if( fd == -1 )
		return false;

	if( grf_file_put_contents_to_fd(file, fd) != grf_file_get_size(file) )
	{
		close(fd);
		return false;
	}

	close(fd);
	return true;
}


GRFEXPORT bool grf_file_rename(GrfNode* handler, const char* newname)
{
	return handler->parent->grf_file_rename(handler, newname);
}


GRFEXPORT bool grf_file_delete(GrfNode* handler)
{
	return handler->parent->grf_file_delete(handler);
}


GRFEXPORT void grf_create_tree(GrfFile* handler)
{
	return handler->GrfFile::grf_create_tree();
}


GRFEXPORT GrfTreeNode* grf_tree_get_root(GrfFile* handler)
{
	return handler->GrfFile::grf_tree_get_root();	
}


GRFEXPORT GrfTreeNode** grf_tree_list_node(GrfTreeNode* node)
{
	size_t num = node->count();
	GrfTreeNode** arr = (GrfTreeNode**)malloc((num+1) * sizeof(GrfTreeNode*));
	node->GrfTreeNode::grf_tree_list_node(arr, num+1);

	return arr;
}


GRFEXPORT bool grf_tree_is_dir(GrfTreeNode* node)
{
	return node->GrfTreeNode::isdir();
}


GRFEXPORT const char* grf_tree_get_name(GrfTreeNode* node)
{
	return node->GrfTreeNode::name();
}


GRFEXPORT GrfNode* grf_tree_get_file(GrfTreeNode* node)
{
	return node->GrfTreeNode::file();
}


GRFEXPORT GrfTreeNode* grf_tree_get_parent(GrfTreeNode* node)
{
	return node->GrfTreeNode::parent();
}


GRFEXPORT GrfTreeNode* grf_file_get_tree(GrfNode* handler)
{
	return handler->GrfNode::get_tree();
}


GRFEXPORT uint32_t grf_tree_dir_count_files(GrfTreeNode* node)
{
	return node->GrfTreeNode::count();
}


GRFEXPORT GrfNode** grf_get_file_list(GrfFile* handler)
{
	size_t num = handler->filecount();
	GrfNode** arr = (GrfNode**)malloc((num+1) * sizeof(GrfNode*));
	handler->GrfFile::grf_get_file_list(arr, num+1);

	return arr;
}


GRFEXPORT GrfNode* grf_get_file_first(GrfFile* handler)
{
	return handler->GrfFile::grf_get_file_first();
}


GRFEXPORT GrfNode* grf_get_file_next(GrfNode* handler)
{
	return handler->GrfNode::get_next();
}


GRFEXPORT GrfNode* grf_get_file_prev(GrfNode* handler)
{
	return handler->GrfNode::get_prev();
}


GRFEXPORT GrfNode** grf_get_file_id_list(GrfFile* handler)
{
	static std::auto_ptr<GrfNode*> p(NULL); // auto-free on program exit
	free(p.release()); // delete previous list
	p = std::auto_ptr<GrfNode*>(grf_get_file_list(handler)); // fill with new list
	return p.get();
}


GRFEXPORT void grf_update_id_list(GrfFile* handler)
{
	return; // do nothing
}


GRFEXPORT uint32_t grf_file_get_id(GrfNode* node)
{
	int i = 0;
	for( GrfNode* n = node; grf_get_file_prev(n) != NULL; n = grf_get_file_prev(n) )
		++i;

	return i;
}


GRFEXPORT GrfNode* grf_get_file_by_id(GrfFile* handler, uint32_t id)
{
	if( id >= grf_filecount(handler) )
		return NULL;

	GrfNode* n;

	int i = 0;
	for( n = grf_get_file_first(handler); i < id && n != NULL; n = grf_get_file_next(n) )
		++i;

	if( i != id )
		return NULL;

	return n;
}


GRFEXPORT bool grf_repack(GrfFile* handler, uint8_t repack_type)
{
	return handler->GrfFile::grf_repack(repack_type);
}


GRFEXPORT bool grf_merge(GrfFile* dest, GrfFile* src, uint8_t repack_type)
{
	return dest->GrfFile::grf_merge(src, repack_type);
}
